package com.conversationboard.model.adminlog;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.LinkedList;
import java.util.List;

import org.joda.time.DateTime;

import com.conversationboard.cache.adminlog.AdminLogCache;
import com.conversationboard.config.Configuration;
import com.conversationboard.database.Database;
import com.conversationboard.database.Queries;
import com.conversationboard.model.Board;
import com.conversationboard.model.Boards;
import com.conversationboard.model.User;

public class AdminLog {

	private static AdminLog instance = null;
	private static final int CACHE_PAGE_SIZE = 25;
	private static final int CACHE_NUMBER_OF_PAGES = 8;


	public static AdminLog getInstance() throws SQLException {

		if (instance == null) {
			instance = new AdminLog();

			/* Populate the cache */

			AdminLogCache cache = AdminLogCache.getInstance();
			cache.setPageSize(CACHE_PAGE_SIZE);
			cache.setNumberOfPages(CACHE_NUMBER_OF_PAGES);
			List<AdminLogEntry> log = instance.get(0, (CACHE_PAGE_SIZE * CACHE_NUMBER_OF_PAGES));
			cache.populate(log);

		}

		return instance;
	}


	public int write(Connection connection, User user, int boardId, String action) throws SQLException {

		/* Check if configured to work and check if authorised to do so */

		Board board = Boards.getBoard(boardId);

		if (boardId == 0) {
			if (!Configuration.getInstance().isLogSiteWideAdminActions()) {
				return 0;
			}
		} else {
			if ((!board.isLogAdminActions()) || (!user.isBoardAdmin(boardId))) {
				return 0;
			}
		}

		/* Insert into Database */

		PreparedStatement statement = connection.prepareStatement(Queries.WRITE_TO_ADMIN_LOG, Statement.RETURN_GENERATED_KEYS);

		if (boardId == 0) {
			statement.setNull(1, Types.INTEGER);
		} else {
			statement.setInt(1, boardId);
		}

		statement.setString(2, user.getLoginId());
		statement.setString(3, user.getDisplayName());
		statement.setString(4, action);
		statement.executeUpdate();

		ResultSet resultSet = statement.getGeneratedKeys();

		int id = 0;
		boolean gotKey = false;

		while (resultSet.next()) {
			id = resultSet.getInt(1);
			gotKey = true;
		}

		if (!gotKey) {
			throw new IllegalStateException("Unable to get autoincrement key from database");
		}

		/* Insert into Cache */

		AdminLogEntry entry = new AdminLogEntry();
		entry.setAction(action);
		entry.setBoardId(boardId);
		entry.setLoginId(user.getLoginId());
		entry.setDisplayName(user.getDisplayName());
		entry.setTimestamp(new Timestamp(new DateTime().getMillis()));
		entry.setId(id);

		AdminLogCache.getInstance().put(entry);

		return id;
		
	}


	public void setReason(Connection connection, int id, String reason) throws SQLException {

		boolean hasConnection = (connection != null);
		
		try {

			if (!hasConnection) {
				connection = Database.getConnection();
			}

			PreparedStatement statement = connection.prepareStatement(Queries.RECORD_REASON_ON_ADMIN_LOG);
			statement.setString(1, reason);
			statement.setInt(2, id);
			statement.executeUpdate();
			
			/* Also update the cache */
			
			AdminLogEntry entry = AdminLogCache.getInstance().getItem(id);
			entry.setReason(reason);

		} finally {
			if (!hasConnection) {
				Database.close(connection);
			}
		}

	}


	public List<AdminLogEntry> getPage(int pageNumber) throws SQLException {

		/* If the page is in the cache, return it from there */

		List<AdminLogEntry> page = null;

		if (pageNumber < CACHE_NUMBER_OF_PAGES) {
			page = AdminLogCache.getInstance().get(pageNumber);
		} else {
			page = this.get((pageNumber * CACHE_PAGE_SIZE), CACHE_PAGE_SIZE);
		}

		return page;
	}


	private List<AdminLogEntry> get(int startAt, int numberOfRecords) throws SQLException {

		Connection connection = null;
		List<AdminLogEntry> log = new LinkedList<AdminLogEntry>();

		try {

			connection = Database.getConnection();

			PreparedStatement statement = connection.prepareStatement(Queries.GET_ADMIN_LOG);
			statement.setInt(1, startAt);
			statement.setInt(2, numberOfRecords);
			statement.execute();

			ResultSet resultSet = statement.getResultSet();

			while (resultSet.next()) {

				AdminLogEntry entry = new AdminLogEntry();
				entry.setId(resultSet.getInt("id"));
				entry.setBoardId(resultSet.getInt("boardid"));
				entry.setAction(resultSet.getString("action"));
				entry.setTimestamp(resultSet.getTimestamp("timestamp"));
				entry.setReason(resultSet.getString("reason"));
				entry.setLoginId(resultSet.getString("loginid"));
				entry.setDisplayName(resultSet.getString("displayname"));

				log.add(entry);

			}

			statement.close();

			return log;

		} finally {
			Database.close(connection);
		}

	}


	public void editReason(User user, int id, String reason) throws SQLException {

		Connection connection = null;

		try {

			connection = Database.getConnection();

			PreparedStatement statement = connection.prepareStatement(Queries.RECORD_REASON_ON_ADMIN_LOG);
			statement.setString(1, reason);
			statement.setInt(2, id);
			statement.executeUpdate();

			statement.close();

			/* Now update the cache too */

			AdminLogEntry entry = AdminLogCache.getInstance().getItem(id);

			if (entry != null) {
				entry.setReason(reason);
			}

		} finally {
			Database.close(connection);
		}
	}


	public AdminLogEntry getItem(int id) throws SQLException {

		AdminLogEntry entry = AdminLogCache.getInstance().getItem(id);

		if (entry != null) {
			return entry;
		} else {

			Connection connection = null;

			try {

				connection = Database.getConnection();

				PreparedStatement statement = connection.prepareStatement(Queries.GET_ADMIN_LOG_ITEM);
				statement.setInt(1, id);
				statement.execute();

				ResultSet resultSet = statement.getResultSet();

				entry = new AdminLogEntry();

				while (resultSet.next()) {
					entry.setId(id);
					entry.setBoardId(resultSet.getInt("boardid"));
					entry.setAction(resultSet.getString("action"));
					entry.setReason(resultSet.getString("reason"));
					entry.setLoginId(resultSet.getString("loginId"));
					entry.setDisplayName(resultSet.getString("displayname"));
					entry.setTimestamp(resultSet.getTimestamp("timestamp"));
				}

				return entry;

			} finally {
				Database.close(connection);
			}

		}

	}

}
